package com.jhe.hexed;

import javax.swing.*;

import org.apache.logging.log4j.LogManager;
import org.apache.logging.log4j.Logger;

import java.awt.*;
import java.awt.event.*;
import java.io.FileInputStream;
import java.io.FileOutputStream;
import java.io.IOException;

/**
 * Created by IntelliJ IDEA.
 * User: laullon
 * Date: 08-abr-2003
 * Time: 13:21:09
 */
@SuppressWarnings("serial")
public class JHexEditor extends JPanel implements FocusListener,AdjustmentListener,MouseWheelListener
{
	static Logger logger = LogManager.getLogger(JHexEditor.class.getName());
    public JHexEditorData data = null;
	protected static Font font=new Font("Monospaced",0,12);
    protected int border=2;
    public boolean DEBUG=false;
    private JPanel panel;
    private JScrollBar sb;
    private int inicio=0;
    private int lineas=10;
	ClipboardInterface clipboard = null;
	public JHexEditorSelection selectionData = new JHexEditorSelection();
	private String logLine = "";

	public JHexEditor(byte[] buff, ClipboardInterface clipboard)
    {
        super();
        data = new JHexEditorData(buff);
        data.setBufferView(0, buff.length);
        
        this.clipboard = clipboard;

        this.addMouseWheelListener(this);

        sb=new JScrollBar(JScrollBar.VERTICAL);
        sb.addAdjustmentListener(this);
        sb.setMinimum(0);
        sb.setMaximum(buff.length/getLineas());

        JPanel p1,p2,p3;
        //centro
        p1=new JPanel(new BorderLayout(1,1));
        p1.add(new JHexEditorHEX(this),BorderLayout.CENTER);
        p1.add(new Columnas(),BorderLayout.NORTH);

        // izq.
        p2=new JPanel(new BorderLayout(1,1));
        p2.add(new Filas(),BorderLayout.CENTER);
        p2.add(new Caja(),BorderLayout.NORTH);

        // der
        p3=new JPanel(new BorderLayout(1,1));
        p3.add(sb,BorderLayout.EAST);
        p3.add(new JHexEditorASCII(this),BorderLayout.CENTER);
        p3.add(new Caja(),BorderLayout.NORTH);

        panel=new JPanel();
        panel.setLayout(new BorderLayout(1,1));
        panel.add(p1,BorderLayout.CENTER);
        panel.add(p2,BorderLayout.WEST);
        panel.add(p3,BorderLayout.EAST);

        this.setLayout(new BorderLayout(1,1));
        this.add(panel,BorderLayout.CENTER);
    }

    public void paint(Graphics g)
    {
        FontMetrics fn=getFontMetrics(font);
        Rectangle rec=this.getBounds();
        lineas=(rec.height/fn.getHeight())-1;
        int n=(data.getBufferLength()/16)-1;
        if(lineas>n) { lineas=n; inicio=0; }

        sb.setValues(getInicio(),+getLineas(),0,data.getBufferLength()/16);
        sb.setValueIsAdjusting(true);
        super.paint(g);
    }

    protected void actualizaCursor()
    {
        int n=(selectionData.cursor/16);

        debugPrint("- "+inicio+"<"+n+"<"+(lineas+inicio)+"("+lineas+")");

        if(n<inicio) inicio=n;
        else if(n>=inicio+lineas) inicio=n-(lineas-1);

        debugPrintLine(" - "+inicio+"<"+n+"<"+(lineas+inicio)+"("+lineas+")");
        
        if (selectionData.isInSelection())
        	selectionData.updateSelection(selectionData.cursor);

        repaint();
    }

	protected void debugPrintLine(String debugString)
	{
		if (DEBUG)
			logger.debug(logLine+debugString);
		logLine = "";
	}

	protected void debugPrint(String debugString)
	{
		if (DEBUG)
			logLine += debugString;
	}

    protected int getInicio()
    {
        return inicio;
    }

    protected int getLineas()
    {
        return lineas;
    }

    protected void fondo(Graphics g,int x,int y,int s)
    {
        FontMetrics fn=getFontMetrics(font);
        g.fillRect(((fn.stringWidth(" ")+1)*x)+border,(fn.getHeight()*y)+border,((fn.stringWidth(" ")+1)*s),fn.getHeight()+1);
    }

    protected void cuadro(Graphics g,int x,int y,int s)
    {
        FontMetrics fn=getFontMetrics(font);
        g.drawRect(((fn.stringWidth(" ")+1)*x)+border,(fn.getHeight()*y)+border,((fn.stringWidth(" ")+1)*s),fn.getHeight()+1);
    }

    protected void printString(Graphics g,String s,int x,int y)
    {
        FontMetrics fn=getFontMetrics(font);
        g.drawString(s,((fn.stringWidth(" ")+1)*x)+border,((fn.getHeight()*(y+1))-fn.getMaxDescent())+border);
    }

    public void focusGained(FocusEvent e)
    {
        this.repaint();
    }

    public void focusLost(FocusEvent e)
    {
        this.repaint();
    }

    public void adjustmentValueChanged(AdjustmentEvent e)
    {
        inicio=e.getValue();
        if(inicio<0) inicio=0;
        repaint();
    }

    public void mouseWheelMoved(MouseWheelEvent e)
    {
        inicio+=(e.getUnitsToScroll());
        if((inicio+lineas)>=data.getBufferLength()/16) inicio=(data.getBufferLength()/16)-lineas;
        if(inicio<0) inicio=0;
        repaint();
    }

    public void keyPressed(KeyEvent e)
    {
        switch(e.getKeyCode())
        {
            case 33:    // rep
                if(selectionData.cursor>=(16*lineas)) selectionData.cursor-=(16*lineas);
                actualizaCursor();
                break;
            case 34:    // fin
                if(selectionData.cursor<(data.getBufferLength()-(16*lineas))) selectionData.cursor+=(16*lineas);
                actualizaCursor();
                break;
            case 35:    // fin
                selectionData.cursor=data.getBufferLength()-1;
                actualizaCursor();
                break;
            case 36:    // ini
                selectionData.cursor=0;
                actualizaCursor();
                break;
            case 37:    // <--
                if(selectionData.cursor!=0) selectionData.cursor--;
                actualizaCursor();
                break;
            case 38:    // <--
                if(selectionData.cursor>15) selectionData.cursor-=16;
                actualizaCursor();
                break;
            case 39:    // -->
                if(selectionData.cursor!=(data.getBufferLength()-1)) selectionData.cursor++;
                actualizaCursor();
                break;
            case 40:    // -->
                if(selectionData.cursor<(data.getBufferLength()-16)) selectionData.cursor+=16;
                actualizaCursor();
                break;
            case 16:	// Shift
            	if (!selectionData.isInSelection())
            	{
            		selectionData.startSelect(selectionData.cursor);
            		repaint();
            	}
            	break;
        }
    }

	public void keyReleased(KeyEvent e)
	{
        if (e.getKeyCode() == 16 && selectionData.isInSelection())
        {
        	selectionData.endSelect(selectionData.cursor);
        	repaint();
        }
	}

    public void goTo(int position)
	{
		if (position < 0)
			position = 0;
		else if (position >= data.getBufferLength())
			position = data.getBufferLength()-1;
		selectionData.cursor = position;
		actualizaCursor();
	}

	public void setClipboardContents(String data)
	{
		if (clipboard != null)
			clipboard.setClipboardContents(data);
		
	}

	public String getClipboardContents()
	{
		if (clipboard != null)
			return clipboard.getClipboardContents();
		return null;
	}

	public boolean hasClipboard()
	{
		return clipboard != null;
	}
	
	void saveData(String fileName, int start, int end)
	{
		FileOutputStream file = null;
		try
		{
			file = new FileOutputStream(fileName);
			data.saveToStream(file, start, end);
		}
		catch (IOException ex)
		{
			JOptionPane.showMessageDialog(this, "Could not write data to file \""+fileName+"\"");
		}
		finally
		{
			if (file != null)
				try { file.close(); } catch (IOException ex) {}
		}
	}
	
	void loadData(String fileName, int pos)
	{
		FileInputStream file = null;
		try
		{
			file = new FileInputStream(fileName);
			data.loadFromStream(file, pos);
		}
		catch (IOException ex)
		{
			JOptionPane.showMessageDialog(this, "Could not read data from file \""+fileName+"\"");
		}
		finally
		{
			if (file != null)
				try { file.close(); } catch (IOException ex) {}
		}
	}
	
	private class Columnas extends JPanel
    {
        public Columnas()
        {
            this.setLayout(new BorderLayout(1,1));
        }
        public Dimension getPreferredSize()
        {
            return getMinimumSize();
        }

        public Dimension getMinimumSize()
        {
            Dimension d=new Dimension();
            FontMetrics fn=getFontMetrics(font);
            int h=fn.getHeight();
            int nl=1;
            d.setSize(((fn.stringWidth(" ")+1)*+((16*3)-1))+(border*2)+1,h*nl+(border*2)+1);
            return d;
        }

        public void paint(Graphics g)
        {
            Dimension d=getMinimumSize();
            g.setColor(Color.white);
            g.fillRect(0,0,d.width,d.height);
            g.setColor(Color.black);
            g.setFont(font);

            for(int n=0;n<16;n++)
            {
                if(n==(selectionData.cursor%16)) cuadro(g,n*3,0,2);
                String s="00"+Integer.toHexString(n);
                s=s.substring(s.length()-2);
                printString(g,s,n*3,0);
            }
        }
    }

    private class Caja extends JPanel
    {
        public Dimension getPreferredSize()
        {
            return getMinimumSize();
        }

        public Dimension getMinimumSize()
        {
            Dimension d=new Dimension();
            FontMetrics fn=getFontMetrics(font);
            int h=fn.getHeight();
            d.setSize((fn.stringWidth(" ")+1)+(border*2)+1,h+(border*2)+1);
            return d;
        }

    }

    private class Filas extends JPanel
    {
        public Filas()
        {
            this.setLayout(new BorderLayout(1,1));
        }
        public Dimension getPreferredSize()
        {
            return getMinimumSize();
        }

        public Dimension getMinimumSize()
        {
            Dimension d=new Dimension();
            FontMetrics fn=getFontMetrics(font);
            int h=fn.getHeight();
            int nl=getLineas();
            d.setSize((fn.stringWidth(" ")+1)*(8)+(border*2)+1,h*nl+(border*2)+1);
            return d;
        }

        public void paint(Graphics g)
        {
            Dimension d=getMinimumSize();
            g.setColor(Color.white);
            g.fillRect(0,0,d.width,d.height);
            g.setColor(Color.black);
            g.setFont(font);

            int ini=getInicio();
            int fin=ini+getLineas();
            int y=0;
            for(int n=ini;n<fin;n++)
            {
                if(n==(selectionData.cursor/16)) cuadro(g,0,y,8);
                String s="0000000000000"+Integer.toHexString(n*16);
                s=s.substring(s.length()-8);
                printString(g,s,0,y++);
            }
        }
    }
}
